#ifndef __CRYGAMESPYSTATS_H__
#define __CRYGAMESPYSTATS_H__

#pragma once

#if USE_CRYLOBBY_GAMESPY

#include "Lobby/CryStats.h"
#include "Lobby/GameSpy/CryGameSpyLobby.h"
#include "Gamespy2.11.00/Source/gp/gp.h"
#include "Gamespy2.11.00/Source/sake/sake.h"

#define MAX_GAMESPY_IDLEN									(8)
#define MAX_GAMESPY_PARALLEL_BOARD_WRITES	(6)						//-- same as max number of leaderboards that can be written at the same time on xbox
#define MAX_GAMESPY_LEADERBOARD_FIELDS	  (10)
#define MAX_GAMESPY_USERDATA_FIELDS				(2)
#define MAX_GAMESPY_SEARCH_STRING_LENGTH	(32)
#define MAX_GAMESPY_PROFILE_SIZE					(3000)				//-- 3000 bytes (not 3K!) to match the Xbox 360 limitation.
#define MAX_GAMESPY_PROFILE_SETTING_BUFFER_SIZE (MAX_GAMESPY_PROFILE_SIZE & ~(15))	//-- In 16 byte blocks, the most we actually have is 2992 bytes, not 3000


class CCryGameSpyStats : public CCryStats, public ICryGameSpyLobbyComponent
{
public:
																	CCryGameSpyStats(CCryLobby* pLobby, CCryLobbyService* pService);
					
					ECryLobbyError					Initialise();
					ECryLobbyError					Terminate();

					void										Tick(CTimeValue tv);

	virtual	ECryLobbyError					StatsRegisterLeaderBoards(SCryStatsLeaderBoardWrite* pBoards, uint32 numBoards, CryLobbyTaskID* pTaskID, CryStatsCallback cb, void* pCbArg);
	virtual	ECryLobbyError					StatsWriteLeaderBoards(CrySessionHandle session, uint32 user, SCryStatsLeaderBoardWrite* pBoards, uint32 numBoards, CryLobbyTaskID* pTaskID, CryStatsCallback cb, void* pCbArg);
	virtual	ECryLobbyError					StatsWriteLeaderBoards(CrySessionHandle session, CryUserID* pUserIDs, SCryStatsLeaderBoardWrite** ppBoards, uint32* pNumBoards, uint32 numUsers, CryLobbyTaskID* pTaskID, CryStatsCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsReadLeaderBoardByRankForRange(CryStatsLeaderBoardID board, uint32 startRank, uint32 num, CryLobbyTaskID* pTaskID, CryStatsReadLeaderBoardCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsReadLeaderBoardByRankForUser(CryStatsLeaderBoardID board, uint32 user, uint32 num, CryLobbyTaskID* pTaskID, CryStatsReadLeaderBoardCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsReadLeaderBoardByUserID(CryStatsLeaderBoardID board, CryUserID* pUserIDs, uint32 numUserIDs, CryLobbyTaskID* pTaskID, CryStatsReadLeaderBoardCallback cb, void* pCbArg);

	virtual ECryLobbyError					StatsRegisterUserData(SCryLobbyUserData* pData, uint32 numData, CryLobbyTaskID* pTaskID, CryStatsCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsWriteUserData(uint32 user, SCryLobbyUserData* pData, uint32 numData, CryLobbyTaskID* pTaskID, CryStatsCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsWriteUserData(CryUserID* pUserIDs, SCryLobbyUserData** ppData, uint32* pNumData, uint32 numUserIDs, CryLobbyTaskID* pTaskID, CryStatsCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsReadUserData(uint32 user, CryLobbyTaskID* pTaskID, CryStatsReadUserDataCallback cb, void* pCbArg);
	virtual ECryLobbyError					StatsReadUserData(uint32 user, CryUserID userID, CryLobbyTaskID* pTaskID, CryStatsReadUserDataCallback cb, void* pCbArg);
	virtual void										SetLeaderboardType( ECryLobbyLeaderboardType leaderboardType );
	virtual ECryLobbyLeaderboardType GetLeaderboardType();

	virtual void										OnGameSpyObjectAvailable( EGameSpyObjectBit object );
	virtual void										OnGameSpyObjectUnavailable( EGameSpyObjectBit object );
	virtual void										OnGameSpyObjectCantMakeAvailable( EGameSpyObjectBit object );


					ECryLobbyError					StartSake();
					ECryLobbyError					SetProfile(int profileId,const char* pLoginTicket);			// Called whenever user logs in
					SAKERequest							SakeSearchForRecords( SAKESearchForRecordsInput* pInput, SAKERequestCallback callback, void* pUserData );

private:
	enum ETask
	{
		eT_StatsRegisterLeaderBoards,
		eT_StatsRegisterUserData,

		eT_StatsWriteLeaderBoard,
		eT_StatsReadLeaderBoard,

		eT_StatsWriteUserData,
		eT_StatsReadUserData,
	};

	// Gamespy requires tables and fields to be set up at the sake admin as follows :
	//
	// All leaderboards should be named LDBnnn where nnn is a number starting at 1
	// Each leaderboard should have a field named SCORE (this is the sorted field) + as many additional fields named FIELDnnn as required
	// A table should be created called STATS with a single binary data field named DATA of size 3000

	struct SRegisterLeaderBoardData
	{
		CryStatsLeaderBoardID								m_boardID;
		CryFixedStringT<MAX_GAMESPY_IDLEN>	m_tableID;		// "LDB1"-"LDBxxxx"		- Gamespy table name
		uint32															m_numColumns;

		struct SColumn
		{
			CryFixedStringT<MAX_GAMESPY_IDLEN>	m_fieldID;		// "FIELD1"-"FIELDxx"
			CryLobbyUserDataID									m_dataID;
			ECryLobbyUserDataType								m_dataType;
			uint16															m_columnID;
		};
		
		SColumn*													m_pColumns;

		uint32														m_recordCount;
		std::map<GPProfile,int>						m_recordIdCache;
	};

	struct SRegisterLeaderBoardDataGroup
	{
		SRegisterLeaderBoardData					m_boards[ eCLLT_Num ];
	};

	struct SRegisterUserData
	{
		uint16														m_id;
		uint16														m_type;
	};

	struct STask;

	struct STaskLink: CMultiThreadRefCount
	{
		CCryGameSpyStats*										pStats;
		STask*															pTask;
	};

	struct STask : public CCryStats::STask
	{
		_smart_ptr< STaskLink >						m_pLink;
		SAKERequest												m_sakeRequest;
		bool															m_haveGPConnection;
		bool															m_taskStarted;
		ECryLobbyLeaderboardType					m_leaderboardType;
	}	m_task[MAX_STATS_TASKS];

	enum EUserInfoBit
	{
		eUIB_LocalIndex = BIT( 0 ),
		eUIB_GPProfile = BIT( 1 ),
		eUIB_Name = BIT( 2 )
	};

	struct SUser
	{
		uint32														m_info;
		uint32														m_localIndex;
		GPProfile													m_profileId;
		char															m_name[ GP_UNIQUENICK_LEN ];
	};

	struct SCryGameSpyOneLeaderBoardWrite
	{
		ECryLobbyLeaderboardType					m_leaderboardType;
		uint32														m_userIdx;
		SAKEGetMyRecordsInput							m_sakeGetInput;
		SAKECreateRecordInput							m_sakeCreateInput;
		SAKEUpdateRecordInput							m_sakeUpdateInput;
		SAKESearchForRecordsInput					m_sakeSearchInput;
		SAKEField													m_sakeField[MAX_GAMESPY_LEADERBOARD_FIELDS];
		char*															m_pFieldNames[MAX_GAMESPY_LEADERBOARD_FIELDS];
		char															m_filter[MAX_GAMESPY_SEARCH_STRING_LENGTH];
	};

	struct SCryGameSpyStatsBoardWriteInfo
	{
		SCryGameSpyOneLeaderBoardWrite*		m_pLeaderBoards;
		uint32														m_curBoard;
		uint32														m_numBoards;
	};

	struct SCryGameSpyStatsBoardReadInfo
	{
		SAKESearchForRecordsInput					m_sakeInput;
		SAKEGetRecordCountInput						m_recCountInput;
		char*															m_pFieldNames[MAX_GAMESPY_LEADERBOARD_FIELDS];
		char															m_searchString[MAX_GAMESPY_SEARCH_STRING_LENGTH];
		uint32														m_numRecordsRequested;
	};

	struct SCryGameSpyStatsUserReadInfo
	{
		GPProfile													m_profileId;
		SAKEGetMyRecordsInput							m_sakeGetInput;
		SAKESearchForRecordsInput					m_sakeSearchInput;
		char*															m_pFieldNames[MAX_GAMESPY_LEADERBOARD_FIELDS];
		char															m_data[MAX_GAMESPY_PROFILE_SIZE];
		char															m_filter[MAX_GAMESPY_SEARCH_STRING_LENGTH];
	};

	struct SCryGameSpyOneUserDataWrite
	{
		ECryLobbyLeaderboardType					m_leaderboardType;
		uint32														m_userIdx;
		SAKEGetMyRecordsInput							m_sakeGetInput;
		SAKECreateRecordInput							m_sakeCreateInput;
		SAKEUpdateRecordInput							m_sakeUpdateInput;
		SAKESearchForRecordsInput					m_sakeSearchInput;
		SAKEField													m_sakeField[ MAX_GAMESPY_USERDATA_FIELDS ];
		char*															m_pFieldNames[ MAX_GAMESPY_USERDATA_FIELDS ];
		char															m_filter[MAX_GAMESPY_SEARCH_STRING_LENGTH];
		uint8*														m_pData;
	};

	struct SCryGameSpyStatsUserDataWriteInfo
	{
		SCryGameSpyOneUserDataWrite*			m_pUserData;
		uint8*														m_pBuf;
		uint32														m_curUserData;
		uint32														m_numUserData;
	};

	CCryGameSpyLobbyService*						GetService() {return (CCryGameSpyLobbyService*)m_pService;}

	ECryLobbyError											StartTask(ETask etask, bool startRunning, uint32 user, CryStatsTaskID* pSTaskID, CryLobbyTaskID* pLTaskID, CrySessionHandle h, void* pCb, void* pCbArg);
	void																StartSubTask(ETask etask, CryStatsTaskID sTaskID);
	void																StartTaskRunning(CryStatsTaskID sTaskID);
	void																EndTask(CryStatsTaskID sTaskID);
	void																StopTaskRunning(CryStatsTaskID sTaskID);

	// Callbacks

	static void													GetRecordCountCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													ReadUserDataCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													SearchForRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													StatsGetCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													StatsUpdateCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													StatsCreateCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													LeaderBoardGetCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													LeaderBoardUpdateCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													LeaderBoardCreateCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void* pInputData, void* pOutputData, void* pUserData);
	static void													GetInfoCallback(GPConnection* pConnection, void* pArg, void* pParam);

	// Start and End Task helpers

	void																StartStatsWriteLeaderBoard(CryStatsTaskID sTaskID);

	void																StartStatsReadLeaderBoard(CryStatsTaskID sTaskID);
	void																EndStatsReadLeaderBoard(CryStatsTaskID sTaskID);

	void																StartStatsWriteUserData(CryStatsTaskID sTaskID);

	void																StartStatsReadUserData(CryStatsTaskID sTaskID);
	void																EndStatsReadUserData(CryStatsTaskID sTaskID);

	// Task helpers

	ECryLobbyError											InitialiseLeaderBoardReadTask(CryStatsLeaderBoardID board,CryStatsTaskID &sTaskID,SRegisterLeaderBoardData** ppRegisteredBoard,SCryGameSpyStatsBoardReadInfo** ppReadInfo,uint32 num, CryLobbyTaskID* pTaskID, CryStatsReadLeaderBoardCallback cb, void* pCbArg);

	SAKEField*													FindFieldFromName(SAKEField* pFields,int numFields,const char* pName);
	CryStatsTaskID											FindTaskFromRequest(SAKERequest request);
	SRegisterLeaderBoardData*						FindRegisteredBoardInfo(const char* pTableId);
	SRegisterLeaderBoardData*						FindRegisteredBoardInfo(CryStatsLeaderBoardID boardId, ECryLobbyLeaderboardType type);
	SRegisterLeaderBoardData::SColumn*	FindColumnFromID(SRegisterLeaderBoardData* pTable,CryLobbyUserDataID id);
	void																ResetRecordData();

	SAKERequest													CreateStatsRecord(CryStatsTaskID sTaskID, SCryGameSpyOneUserDataWrite* pWriteInfo);
	SAKERequest													UpdateStatsRecord(CryStatsTaskID sTaskID, SCryGameSpyOneUserDataWrite* pWriteInfo);
	void																NextStatsWrite( CryStatsTaskID taskId );
	SAKERequest													FindLeaderBoardRecord(CryStatsTaskID sTaskID, SCryGameSpyStatsBoardWriteInfo* pWriteInfo);
	SAKERequest													UpdateLeaderBoardRecord(STask* pTask, SCryGameSpyStatsBoardWriteInfo* pWriteInfo);
	SAKERequest													CreateLeaderBoardRecord(SCryGameSpyStatsBoardWriteInfo* pWriteInfo);

	ECryLobbyError											StatsWriteLeaderBoards(CryStatsTaskID	sTaskID, SCryStatsLeaderBoardWrite** ppBoards, uint32* pNumBoards, uint32 numUsers);
	bool																QueueNextLeaderBoard(CryStatsTaskID sTaskID, SCryGameSpyStatsBoardWriteInfo* pWriteInfo,SAKERequest* pRequest);
	void																QueueNextLeaderBoardSake( CryStatsTaskID sTaskID, SCryGameSpyStatsBoardWriteInfo* pWriteInfo,SAKERequest* pRequest );
	ECryLobbyError											BuildLeaderBoardWriteArray( CryStatsTaskID sTaskID, uint32 numUsers, uint32 userIdx, SCryStatsLeaderBoardWrite** ppBoards, uint32* pNumBoards, uint32 boardIdx, SCryGameSpyOneLeaderBoardWrite** ppWrites, uint32* pNumWrites );

	ECryLobbyError											StatsWriteUserData( CryStatsTaskID sTaskID, SCryLobbyUserData** ppData, uint32* pNumData, uint32 numUserIDs);
	ECryLobbyError											BuildStatsWriteArray( CryStatsTaskID sTaskID, uint32 numUsers, uint32 userIdx, SCryLobbyUserData** ppData, uint32* pNumData, SCryGameSpyOneUserDataWrite** ppWrites, uint8** ppBuf, uint32 bufLen );
	void																StartNextStatsWrite( CryStatsTaskID taskId, STask* pTask, SCryGameSpyStatsUserDataWriteInfo* pWriteInfo );
	void																StartCurrentStatsWrite( CryStatsTaskID sTaskID, STask* pTask );
	void																PopulateUserDataWriteFields( CryStatsTaskID sTaskID, SCryGameSpyOneUserDataWrite* pWrite, int* pNumFields );
	void																FindOwnStatsRecord( STask* pTask, SCryGameSpyOneUserDataWrite* pWrite );
	void																FindOthersStatsRecord( STask* pTask, SCryGameSpyOneUserDataWrite* pWrite );
	ECryLobbyError											StatsReadUserData( SUser* pUser, CryLobbyTaskID* pTaskID, CryStatsReadUserDataCallback cb, void* pCbArg );

	virtual void												FreeTask( CryStatsTaskID sTaskID );
	ECryLobbyError											CreateTaskLink( STask* pTask );
	void																PassTaskLinkToGameSpy( const _smart_ptr< STaskLink>& pLink );
	void																FreeTaskLinkFromGameSpy( STaskLink* pLink );

	SAKE																m_sake;

	std::vector< _smart_ptr< STaskLink > >	m_taskLinks;
	DynArray<SRegisterLeaderBoardDataGroup>	m_leaderBoards;
	DynArray<SRegisterUserData>					m_userData;
	uint32															m_numLeaderBoards;
	uint32															m_numUserData;
	uint32															m_nCumulativeUserDataSize;

	std::map<GPProfile,int>							m_statsRecordIdCache[ eCLLT_Num ];
	int																	m_profileId;

	const char*													m_pGameSpyTableP2PNameFormat;
	const char*													m_pGameSpyTableDedicatedNameFormat;
	const char*													m_pGameSpyStatsP2PTableName;
	const char*													m_pGameSpyStatsDedicatedTableName;

	ECryLobbyLeaderboardType						m_leaderboardType;
};

#endif //USE_CRYLOBBY_GAMESPY

#endif // __CRYGAMESPYSTATS_H__
